#include <iostream>

#include <ctime>
#include <sys/time.h>
#include <cstdlib>
#include <cassert>
#include "tile.h"
#include "puzzle.h"

using namespace std;

Puzzle::Puzzle(unsigned int size) {
	assert(size > 0);
	this->size = size;
	this->halfWidth = size;
	this->width = size * 2;
	this->height = size;
	this->board.resize(width);
	for(int x = 0; x < this->width; x++) {
		this->board[x].resize(this->height);
		for(int y = 0; y < this->height; y++) {
			if(x < this->halfWidth) {
				this->board[x][y] = new Tile(x, y);
				this->board[x][y]->setBasePosition(x, y); //solved position
			} else {
				this->board[x][y] = 0;
			}
		}
	}
	this->buildGameBoard();
}

void Puzzle::buildGameBoard() {
	struct timeval tv;
	gettimeofday(&tv, NULL);
	srand(tv.tv_sec + tv.tv_usec);
	int maxrange = 10;
	for(int x = 0; x < this->halfWidth; x++) {
		for(int y = 0; y <= this->height; y++) {
			int n = rand() % maxrange;
			if(y - 1 >= 0) {
				this->board[x][y - 1]->setSouth(n);
			}
			if(y < this->height) {
				this->board[x][y]->setNorth(n);
			}
		}
	}
	for(int x = 0; x <= this->halfWidth; x++) {
		for(int y = 0; y < this->height; y++) {
			int n = rand() % maxrange;
			if(x - 1 >= 0) {
				this->board[x - 1][y]->setEast(n);
			}
			if(x < this->halfWidth) {
				this->board[x][y]->setWest(n);
			}
		}
	}

	vector<Tile* > tiles;
	for(int x = 0; x < this->halfWidth; x++) {
		for(int y = 0; y < this->height; y++) {
			tiles.push_back(this->board[x][y]);
			this->board[x][y] = 0;
		}
	}
	for(int x = 0; x < this->halfWidth; x++) {
		for(int y = 0; y < this->height; y++) {
			int n = rand() % tiles.size();
			int xt = x + this->halfWidth;
			this->board[xt][y] = tiles.at(n);
			this->board[xt][y]->setPosition(xt, y);
			this->board[xt][y]->setOriginPosition(xt, y); //puzzle position
			tiles.erase(tiles.begin() + n);
		}
	}
}

void Puzzle::newGame() {
	cout << "init board first" << endl;
	vector<Tile* > tiles;
	for(int x = 0; x < this->width; x++) {
		for(int y = 0; y < this->height; y++) {
			if(this->board[x][y] != 0) {
				tiles.push_back(this->board[x][y]);
				this->board[x][y] = 0;
			}
		}
	}
	int mSize = tiles.size();
	for(int i = 0; i < mSize; i++) {
		Tile *tile = tiles[i];
		unsigned int mBaseX = tile->getBaseX();
		unsigned int mBaseY = tile->getBaseY();
		tile->setPosition(mBaseX, mBaseY);
		this->board[mBaseX][mBaseY] = tile;
	}
	this->buildGameBoard();
}

void Puzzle::reset() {
	vector<Tile* > tiles;
	for(int x = 0; x < this->width; x++) {
		for(int y = 0; y < this->height; y++) {
			if(this->board[x][y] != 0) {
				tiles.push_back(this->board[x][y]);
				this->board[x][y] = 0;
			}
		}
	}
	int mSize = tiles.size();
	for(int i = 0; i < mSize; i++) {
		Tile *tile = tiles[i];
		unsigned int mOriginX = tile->getOriginX();
		unsigned int mOriginY = tile->getOriginY();
		tile->setPosition(mOriginX, mOriginY);
		this->board[mOriginX][mOriginY] = tile;
	}
}

vector<vector<Tile*> > Puzzle::getBoard() {
	return this->board;
}

bool Puzzle::move(unsigned int x0, unsigned int y0, unsigned int x1, unsigned int y1) {
	assert(x0 >= 0 && x0 < this->width && y0 >= 0 && y0 < this->height && x1 >= 0 && x1 < this->width && y1 >= 0 && y1 < this->height);
	
	if(this->board[x1][y1] != 0) {
		return false;
	}

	Tile *tile = this->board[x0][y0];
	if(tile == 0){
		return false;
	}

	if(x1 < this->halfWidth && !this->tileFits(x0, y0, x1, y1)){
		return false;
	}

	this->board[x1][y1] = tile;
	tile->setPosition(x1, y1);
	this->board[x0][y0] = 0;
	return true;
}

bool Puzzle::tileFits(unsigned int x0, unsigned int y0, unsigned int x1, unsigned int y1) {
	assert(x0 >= 0 && x0 < this->width && y0 >= 0 && y0 < this->height && x1 >= 0 && x1 < this->width && y1 >= 0 && y1 < this->height);

	Tile *tile = this->board[x0][y0];
	if(tile == 0) {
		return false;
	}

	if(x1 > 0 && !(x1 - 1 == x0 && y1 == y0) && this->board[x1 - 1][y1] != 0 && this->board[x1 - 1][y1]->getEast() != tile->getWest()) {
		return false;
	}
	if(x1 < this->halfWidth - 1 && !(x1 + 1 == x0 && y1 == y0) && this->board[x1 + 1][y1] != 0 && this->board[x1 + 1][y1]->getWest() != tile->getEast()) {
		return false;
	}
	if(y1 > 0 && !(x1 == x0 && y1 - 1 == y0) && this->board[x1][y1 - 1] != 0 && this->board[x1][y1 - 1]->getSouth() != tile->getNorth()) {
		return false;
	}
	if(y1 < height - 1 && !(x1 == x0 && y1 + 1 == y0) && this->board[x1][y1 + 1] != 0 && this->board[x1][y1 + 1]->getNorth() != tile->getSouth()) {
		return false;
	}
	return true;
}

bool Puzzle::isSolved() {
	for(int x = 0; x < this->halfWidth; x++) {
		for(int y = 0; y < this->height; y++) {
			if(this->board[x][y] == 0) {
				return false;
			}
		}
	}
	return true;
}

void Puzzle::print() {
	for(int x = 0; x < width; x++) {
		for(int y = 0; y < height; y++) {
			Tile *tile = this->board[x][y];
			if(tile != 0) {
				cout << tile->getBaseX() << ", " << tile->getBaseY() << " | " << tile->getOriginX() << ", " << tile->getOriginY() << " | " << tile->getX() << ", " << tile->getY() << " | " << tile->getNorth() << ", " << tile->getEast() << ", " << tile->getSouth() << ", " << tile->getWest() << endl;
			}
		}
	}
}

Puzzle::~Puzzle() {
	for(int x = 0; x < this->width; x++) {
		for(int y = 0; y < this->height; y++) {
			if(this->board[x][y] != 0){
				delete this->board[x][y];
			}
		}
	}
}
